/**
 * 
 */
package org.statewalker.parser.wiki.grammar;

import org.statewalker.dict.BaseDict;
import org.statewalker.validator.AbstractGrammar;
import org.statewalker.validator.ValidatorGrammarConfig;

/**
 * @author kotelnikov
 */
public class WikiBlockGrammar extends AbstractGrammar {

    public WikiBlockGrammar(ValidatorGrammarConfig config) {
        this(config, BaseDict.ROOT);
    }

    public WikiBlockGrammar(ValidatorGrammarConfig config, String root) {
        super(config, root);
    }

    public WikiBlockGrammar(
        ValidatorGrammarConfig config,
        WikiBlockDict block,
        WikiInlineDict inline) {
        super(config);
    }

    private void addBlockElements(String key) {
        addInlineExtended(key);
        defineTransition(key, WikiBlockDict.SECTION1, BaseDict.FINAL);
        defineTransition(key, WikiBlockDict.DEFAULT_SECTION, BaseDict.FINAL);
        addInternalDocument(key);
    }

    private void addContentElements(String key) {
        addInternalDocument(key);
        defineSubstates(key, WikiBlockDict.P);
        defineSubstates(key, WikiBlockDict.PRE);
        defineSubstates(key, WikiBlockDict.BLOCKQUOTE);
        defineSubstates(key, WikiBlockDict.TABLE);
        defineSubstates(key, WikiBlockDict.HR);
        addLists(key);
    }

    private void addDocumentContent(String key) {
        defineTransition(
            key,
            BaseDict.INITIAL,
            BaseDict.ANY,
            WikiBlockDict.DEFAULT_SECTION);
        defineTransition(key, WikiBlockDict.SECTION1, WikiBlockDict.SECTION1);
        defineTransition(key, BaseDict.ANY, BaseDict.ERROR);
    }

    private void addInlineExtended(String key) {
        defineSubstates(key, WikiInlineDict.STYLE);
    }

    private void addInternalDocument(String key) {
        defineSubstates(key, WikiBlockDict.DOCUMENT);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_lists
    // <!ENTITY % lists "ul | ol | dl">
    private void addLists(String key) {
        defineSubstates(
            key,
            WikiBlockDict.UL,
            WikiBlockDict.OL,
            WikiBlockDict.DL);
    }

    private String getBlockParent() {
        return WikiBlockDict.DEFAULT_CONTENT;
    }

    @Override
    public void init() {
        initRoot();
        initDOCUMENT();

        // Sections
        initDEFAULT_SECTION();
        initSECTIONS();

        // Blocks
        initBLOCKQUOTE();
        initDL();
        initDD();
        initDT();
        initEMPTY_LINES();
        initHR();
        initLI();
        initOL();
        initP();
        initPRE();
        initTABLE();
        initTD();
        initTH();
        initTR();
        initUL();

        // Additional transitions for styles
        initSTYLE();
    }

    protected void initBLOCKQUOTE() {
        init(WikiBlockDict.BLOCKQUOTE);
        defineParent(WikiBlockDict.BLOCKQUOTE, getBlockParent());
        addBlockElements(WikiBlockDict.BLOCKQUOTE);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_dd
    // <!ELEMENT dd %Flow;>
    protected void initDD() {
        init(WikiBlockDict.DD);
        defineParent(WikiBlockDict.DD, WikiBlockDict.DL);
        addBlockElements(WikiBlockDict.DD);
        addLists(WikiBlockDict.DD);
        if (useExtendedRules()) {
            defineTransition(WikiBlockDict.DD, WikiBlockDict.DD, BaseDict.FINAL);
            defineTransition(WikiBlockDict.DD, WikiBlockDict.DT, BaseDict.FINAL);
        }
    }

    protected void initDEFAULT_SECTION() {
        init(WikiBlockDict.DEFAULT_SECTION);
        defineParent(WikiBlockDict.DEFAULT_SECTION, WikiBlockDict.DOCUMENT);
        defineParent(
            WikiBlockDict.DEFAULT_CONTENT,
            WikiBlockDict.DEFAULT_SECTION);
        defineTransition(
            WikiBlockDict.DEFAULT_SECTION,
            BaseDict.INITIAL,
            BaseDict.ANY,
            WikiBlockDict.DEFAULT_CONTENT);
        init(WikiBlockDict.DEFAULT_CONTENT);
        addContentElements(WikiBlockDict.DEFAULT_CONTENT);
        // Closes the default section if there is a defined section
        for (int j = 0; j < WikiBlockDict.SECTIONS.length; j++) {
            defineTransition(
                WikiBlockDict.DEFAULT_CONTENT,
                WikiBlockDict.SECTIONS[j],
                BaseDict.FINAL);
        }
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_dl
    // <!ELEMENT dl (dt|dd)+>
    protected void initDL() {
        init(WikiBlockDict.DL);
        defineParent(WikiBlockDict.DL, getBlockParent());
        defineSubstates(WikiBlockDict.DL, WikiBlockDict.DD, WikiBlockDict.DT);
        // By default (if it is not a "dd" or "dt" tag) the list should
        // create a "dd" element.
        defineTransition(
            WikiBlockDict.DL,
            BaseDict.INITIAL,
            BaseDict.ANY,
            WikiBlockDict.DD);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_html
    // <!ELEMENT html (head, body)>
    protected void initDOCUMENT() {
        init(WikiBlockDict.DOCUMENT);
        defineParent(WikiBlockDict.DOCUMENT, getROOT());
        addDocumentContent(WikiBlockDict.DOCUMENT);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_dt
    // <!ELEMENT dt %Inline;>
    protected void initDT() {
        init(WikiBlockDict.DT);
        defineParent(WikiBlockDict.DT, WikiBlockDict.DL);
        addInlineExtended(WikiBlockDict.DT);
    }

    protected void initEMPTY_LINES() {
        init(WikiBlockDict.EMPTY_LINES);
        defineParent(WikiBlockDict.EMPTY_LINES, getBlockParent());
    }

    protected void initHR() {
        init(WikiBlockDict.HR);
        defineParent(WikiBlockDict.HR, getBlockParent());
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_li
    // <!ELEMENT li %Flow;>
    protected void initLI() {
        init(WikiBlockDict.LI);
        defineParent(WikiBlockDict.LI, WikiBlockDict.UL);
        addBlockElements(WikiBlockDict.LI);
        addLists(WikiBlockDict.LI);
        if (useExtendedRules()) {
            defineTransition(WikiBlockDict.LI, WikiBlockDict.LI, BaseDict.FINAL);
        }
    }

    private void initList(String key) {
        defineParent(key, getBlockParent());
        defineSubstates(key, WikiBlockDict.LI);
        defineMandatoryTransition(key, BaseDict.INITIAL, WikiBlockDict.LI);

        if (useExtendedRules()) {
            // Open a "li" element if there is a new list
            defineTransition(key, WikiBlockDict.UL, WikiBlockDict.LI);
            defineTransition(key, WikiBlockDict.OL, WikiBlockDict.LI);
            defineTransition(key, WikiBlockDict.DL, WikiBlockDict.LI);
        }
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_ol
    // <!ELEMENT ul (li)+>
    protected void initOL() {
        init(WikiBlockDict.OL);
        initList(WikiBlockDict.OL);
    }

    protected void initP() {
        init(WikiBlockDict.P);
        String parent = getBlockParent();
        defineParent(WikiBlockDict.P, parent);
        addInlineExtended(WikiBlockDict.P);
    }

    protected void initPRE() {
        init(WikiBlockDict.PRE);
        defineParent(WikiBlockDict.PRE, getBlockParent());
    }

    protected void initRoot() {
        defineMandatoryTransition(
            getROOT(),
            BaseDict.INITIAL,
            WikiBlockDict.DOCUMENT);
    }

    protected void initSECTIONS() {
        String parent = WikiBlockDict.DOCUMENT;
        for (int i = 0; i < WikiBlockDict.SECTIONS.length; i++) {
            init(WikiBlockDict.SECTIONS[i]);
            defineParent(WikiBlockDict.SECTIONS[i], parent);
            defineParent(WikiBlockDict.CONTENTS[i], WikiBlockDict.SECTIONS[i]);
            defineParent(WikiBlockDict.HEADS[i], WikiBlockDict.SECTIONS[i]);

            // Section
            defineTransition(
                WikiBlockDict.SECTIONS[i],
                BaseDict.INITIAL,
                BaseDict.ANY,
                WikiBlockDict.HEADS[i]);
            defineTransition(
                WikiBlockDict.SECTIONS[i],
                WikiBlockDict.HEADS[i],
                BaseDict.ANY,
                WikiBlockDict.CONTENTS[i]);
            defineTransition(
                WikiBlockDict.SECTIONS[i],
                WikiBlockDict.HEADS[i],
                BaseDict.FINAL,
                WikiBlockDict.CONTENTS[i]);

            // Heads
            addInlineExtended(WikiBlockDict.HEADS[i]);

            // Content
            init(WikiBlockDict.CONTENTS[i]);
            addContentElements(WikiBlockDict.CONTENTS[i]);
            defineTransition(
                WikiBlockDict.CONTENTS[i],
                BaseDict.ANY,
                WikiBlockDict.SECTION1,
                BaseDict.ERROR);
            for (int j = 0; j <= i; j++) {
                defineTransition(
                    WikiBlockDict.CONTENTS[i],
                    WikiBlockDict.SECTIONS[j],
                    BaseDict.FINAL);
            }
            if (i < WikiBlockDict.SECTIONS.length - 1) {
                defineTransition(
                    WikiBlockDict.CONTENTS[i],
                    WikiBlockDict.SECTIONS[i + 1],
                    WikiBlockDict.SECTIONS[i + 1]);
            }

            parent = WikiBlockDict.CONTENTS[i];
        }
    }

    protected void initSTYLE() {
        init(WikiInlineDict.STYLE);
        defineParent(WikiInlineDict.STYLE, WikiBlockDict.P);
    }

    protected void initTABLE() {
        init(WikiBlockDict.TABLE);
        defineParent(WikiBlockDict.TABLE, getBlockParent());
        defineMandatoryTransition(
            WikiBlockDict.TABLE,
            BaseDict.INITIAL,
            WikiBlockDict.TR);
        defineTransition(
            WikiBlockDict.TABLE,
            WikiBlockDict.TR,
            WikiBlockDict.TR,
            WikiBlockDict.TR);
    }

    private void initTableCell(String key) {
        defineParent(key, WikiBlockDict.TR);
        addBlockElements(key);
        if (useExtendedRules()) {
            // These additional transitions are defined to close an existing
            // table cell when an another "tr", "td" or "th" element is
            // occurred.
            String finalKey = BaseDict.FINAL;
            defineTransition(key, WikiBlockDict.TD, finalKey);
            defineTransition(key, WikiBlockDict.TH, finalKey);
            defineTransition(key, WikiBlockDict.TR, finalKey);
        }
    }

    protected void initTD() {
        init(WikiBlockDict.TD);
        initTableCell(WikiBlockDict.TD);
    }

    protected void initTH() {
        init(WikiBlockDict.TH);
        initTableCell(WikiBlockDict.TH);
    }

    protected void initTR() {
        init(WikiBlockDict.TR);
        defineParent(WikiBlockDict.TR, WikiBlockDict.TABLE);
        defineSubstates(WikiBlockDict.TR, WikiBlockDict.TD, WikiBlockDict.TH);
        defineMandatoryTransition(
            WikiBlockDict.TR,
            BaseDict.INITIAL,
            WikiBlockDict.TD);
        if (useExtendedRules()) {
            // Exit from the state when a new row is reported.
            // (It should create a new row in the same table and not create
            // a new embedded table);
            defineTransition(WikiBlockDict.TR, WikiBlockDict.TR, BaseDict.FINAL);
        }
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_ul
    // <!ELEMENT ul (li)+>
    protected void initUL() {
        init(WikiBlockDict.UL);
        initList(WikiBlockDict.UL);
    }

    protected boolean useExtendedRules() {
        return true;
    }

}
